文章内容:用 webpack 打包含有动态加载的模块,分析打包后的代码,也就是 webpack 运行时代码。

先思考如下一些问题:

  1. 什么叫做动态加载
  2. 如果不使用 webpack 打包,能做到动态加载吗
  3. webpack 是如何实现动态加载的
  4. webpack 处理动态加载与静态 import 的区别是什么

# 代码准备

index.js

import test from "./test";

console.log("test", test);

const loadSum = () => {
  import("./sum").then((module) => {
    console.log(module.default(6, 9));
  });
};

const btn = document.getElementById("btn");
btn.addEventListener("click", loadSum, false);
1
2
3
4
5
6
7
8
9
10
11
12

test.js

export default "test";
1

sum.js

const sum = (a, b) => {
  return a + b;
};

export default sum;
1
2
3
4
5

webpack.config.js

const path = require("path");
module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "build"),
  },
  mode: "none",
};
1
2
3
4
5
6
7
8

在控制台执行

npx webpack
1

输出 build/main.js 和 build/1.js

build/main.js

/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ('test');

/***/ })
/******/ 	]);
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = __webpack_modules__;
/******/
/************************************************************************/
/******/ 	/* webpack/runtime/define property getters */
/******/ 	(() => {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = (exports, definition) => {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	})();
/******/
/******/ 	/* webpack/runtime/ensure chunk */
/******/ 	(() => {
/******/ 		__webpack_require__.f = {};
/******/ 		// This file contains only the entry chunk.
/******/ 		// The chunk loading function for additional chunks
/******/ 		__webpack_require__.e = (chunkId) => {
/******/ 			return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
/******/ 				__webpack_require__.f[key](chunkId, promises);
/******/ 				return promises;
/******/ 			}, []));
/******/ 		};
/******/ 	})();
/******/
/******/ 	/* webpack/runtime/get javascript chunk filename */
/******/ 	(() => {
/******/ 		// This function allow to reference async chunks
/******/ 		__webpack_require__.u = (chunkId) => {
/******/ 			// return url for filenames based on template
/******/ 			return "" + chunkId + ".js";
/******/ 		};
/******/ 	})();
/******/
/******/ 	/* webpack/runtime/global */
/******/ 	(() => {
/******/ 		__webpack_require__.g = (function() {
/******/ 			if (typeof globalThis === 'object') return globalThis;
/******/ 			try {
/******/ 				return this || new Function('return this')();
/******/ 			} catch (e) {
/******/ 				if (typeof window === 'object') return window;
/******/ 			}
/******/ 		})();
/******/ 	})();
/******/
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	(() => {
/******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ 	})();
/******/
/******/ 	/* webpack/runtime/load script */
/******/ 	(() => {
/******/ 		var inProgress = {};
/******/ 		// data-webpack is not used as build has no uniqueName
/******/ 		// loadScript function to load a script via script tag
/******/ 		__webpack_require__.l = (url, done, key, chunkId) => {
/******/ 			if(inProgress[url]) { inProgress[url].push(done); return; }
/******/ 			var script, needAttach;
/******/ 			if(key !== undefined) {
/******/ 				var scripts = document.getElementsByTagName("script");
/******/ 				for(var i = 0; i < scripts.length; i++) {
/******/ 					var s = scripts[i];
/******/ 					if(s.getAttribute("src") == url) { script = s; break; }
/******/ 				}
/******/ 			}
/******/ 			if(!script) {
/******/ 				needAttach = true;
/******/ 				script = document.createElement('script');
/******/
/******/ 				script.charset = 'utf-8';
/******/ 				script.timeout = 120;
/******/ 				if (__webpack_require__.nc) {
/******/ 					script.setAttribute("nonce", __webpack_require__.nc);
/******/ 				}
/******/
/******/ 				script.src = url;
/******/ 			}
/******/ 			inProgress[url] = [done];
/******/ 			var onScriptComplete = (prev, event) => {
/******/ 				// avoid mem leaks in IE.
/******/ 				script.onerror = script.onload = null;
/******/ 				clearTimeout(timeout);
/******/ 				var doneFns = inProgress[url];
/******/ 				delete inProgress[url];
/******/ 				script.parentNode && script.parentNode.removeChild(script);
/******/ 				doneFns && doneFns.forEach((fn) => (fn(event)));
/******/ 				if(prev) return prev(event);
/******/ 			};
/******/ 			var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
/******/ 			script.onerror = onScriptComplete.bind(null, script.onerror);
/******/ 			script.onload = onScriptComplete.bind(null, script.onload);
/******/ 			needAttach && document.head.appendChild(script);
/******/ 		};
/******/ 	})();
/******/
/******/ 	/* webpack/runtime/make namespace object */
/******/ 	(() => {
/******/ 		// define __esModule on exports
/******/ 		__webpack_require__.r = (exports) => {
/******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 			}
/******/ 			Object.defineProperty(exports, '__esModule', { value: true });
/******/ 		};
/******/ 	})();
/******/
/******/ 	/* webpack/runtime/publicPath */
/******/ 	(() => {
/******/ 		var scriptUrl;
/******/ 		if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
/******/ 		var document = __webpack_require__.g.document;
/******/ 		if (!scriptUrl && document) {
/******/ 			if (document.currentScript)
/******/ 				scriptUrl = document.currentScript.src
/******/ 			if (!scriptUrl) {
/******/ 				var scripts = document.getElementsByTagName("script");
/******/ 				if(scripts.length) scriptUrl = scripts[scripts.length - 1].src
/******/ 			}
/******/ 		}
/******/ 		// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
/******/ 		// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
/******/ 		if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
/******/ 		scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/?.*$/, "").replace(//[^/]+$/, "/");
/******/ 		__webpack_require__.p = scriptUrl;
/******/ 	})();
/******/
/******/ 	/* webpack/runtime/jsonp chunk loading */
/******/ 	(() => {
/******/ 		// no baseURI
/******/
/******/ 		// object to store loaded and loading chunks
/******/ 		// undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ 		// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
            // installedChunks 存放 已经加载了 和 正在加载 的 chunks
            // 有几种取值
            // 0                          -> 已经加载了的 chunk
            // [resolve, reject, Promise] -> 正在加载的 chunk
            // undefined                  -> chunk 没有加载
            // null                       -> chunk preloaded/prefetched
/******/ 		var installedChunks = {
/******/ 			0: 0
/******/ 		};
/******/
/******/ 		__webpack_require__.f.j = (chunkId, promises) => {
/******/ 				// JSONP chunk loading for javascript
/******/ 				var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
/******/ 				if(installedChunkData !== 0) { // 0 means "already installed".
/******/
/******/ 					// a Promise means "currently loading".
/******/ 					if(installedChunkData) {
/******/ 						promises.push(installedChunkData[2]);
/******/ 					} else {
/******/ 						if(true) { // all chunks have JS
/******/ 							// setup Promise in chunk cache
                      // installedChunkData 先赋值为 [resolve, reject]
                      // 注意这里的 resolve,有用,后面在成功加载到 1.js 后就是执行这个 resolve(),改变了 promise 的状态
/******/ 							var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
                      // installedChunkData -> [resolve, reject, Promise]
                      // 这个时候 promise 实例的状态为 pending
/******/ 							promises.push(installedChunkData[2] = promise);
/******/
/******/ 							// start chunk loading
                      // 得到需要加载  chunk 的 url
/******/ 							var url = __webpack_require__.p + __webpack_require__.u(chunkId);
/******/ 							// create error before stack unwound to get useful stacktrace later
                      // 进行错误处理 -> 未成功加载到某个 chunk
/******/ 							var error = new Error();
/******/ 							var loadingEnded = (event) => {
/******/ 								if(__webpack_require__.o(installedChunks, chunkId)) {
/******/ 									installedChunkData = installedChunks[chunkId];
/******/ 									if(installedChunkData !== 0) installedChunks[chunkId] = undefined;
/******/ 									if(installedChunkData) {
/******/ 										var errorType = event && (event.type === 'load' ? 'missing' : event.type);
/******/ 										var realSrc = event && event.target && event.target.src;
/******/ 										error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
/******/ 										error.name = 'ChunkLoadError';
/******/ 										error.type = errorType;
/******/ 										error.request = realSrc;
/******/ 										installedChunkData[1](error);
/******/ 									}
/******/ 								}
/******/ 							};
                      // 调用 __webpack_require__.l
/******/ 							__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
/******/ 						} else installedChunks[chunkId] = 0;
/******/ 					}
/******/ 				}
/******/ 		};
/******/
/******/ 		// no prefetching
/******/
/******/ 		// no preloaded
/******/
/******/ 		// no HMR
/******/
/******/ 		// no HMR manifest
/******/
/******/ 		// no on chunks loaded
/******/
/******/ 		// install a JSONP callback for chunk loading
/******/ 		var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
/******/ 			var [chunkIds, moreModules, runtime] = data;
/******/ 			// add "moreModules" to the modules object,
/******/ 			// then flag all "chunkIds" as loaded and fire callback
/******/ 			var moduleId, chunkId, i = 0;
/******/ 			if(chunkIds.some((id) => (installedChunks[id] !== 0))) {
/******/ 				for(moduleId in moreModules) {
/******/ 					if(__webpack_require__.o(moreModules, moduleId)) {
/******/ 						__webpack_require__.m[moduleId] = moreModules[moduleId];
/******/ 					}
/******/ 				}
/******/ 				if(runtime) var result = runtime(__webpack_require__);
/******/ 			}
/******/ 			if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
/******/ 			for(;i < chunkIds.length; i++) {
/******/ 				chunkId = chunkIds[i];
/******/ 				if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
/******/ 					installedChunks[chunkId][0]();
/******/ 				}
/******/ 				installedChunks[chunkId] = 0;
/******/ 			}
/******/
/******/ 		}
/******/
/******/ 		var chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || [];
/******/ 		chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
/******/ 		chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
/******/ 	})();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);


console.log("test", _test__WEBPACK_IMPORTED_MODULE_0__["default"]);

const loadSum = () => {
  __webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(__webpack_require__, 2)).then(module => {
    console.log(module.default(6, 9))
  })
}

const btn = document.getElementById("btn");
btn.addEventListener("click", loadSum, false);

})();

/******/ })()
;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297

build/1.js

"use strict";
(self["webpackChunk"] = self["webpackChunk"] || []).push([
  [1],
  {
    /***/ 2: /***/ (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
        /* harmony export */
      });
      const sum = (a, b) => {
        return a + b;
      };

      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = sum;

      /***/
    },
  },
]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

这里的示例,test.js 是通过静态 import 的方式引入的,sum.js 是通过动态 import 的方式引入的,可以看到一个很明显的区别:

  • test 模块一开始就放到了 __webpack_modules__中,而 sum 模块被单独打包成了一个独立的文件 1.js 中

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="btn">load sum</button>
    <script src="./build/main.js"></script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13

由于这里涉及到 DOM 的一些操作,所以需要到浏览器中调试,这里给出两种方式:

  1. npm i serve,然后在控制台执行 npx serve
  2. 安装 VS Code 插件 Live Server,点击右下角 go live,打开对应的 html

# 加载 sum 模块流程分析

sum.js 源码

import("./sum").then((module) => {
  console.log(module.default(6, 9));
});
1
2
3

webpack 运行时代码,加载 sum 模块

__webpack_require__
  .e(/* import() */ 1)
  .then(__webpack_require__.bind(__webpack_require__, 2))
  .then((module) => {
    console.log(module.default(6, 9));
  });
1
2
3
4
5
6

可以将上部分代码进行拆分一下,更有利于我们分析

const p1 = __webpack_require__.e(/* import() */ 1);
console.log("p1", p1);
const fn = __webpack_require__.bind(__webpack_require__, 2);
console.log("fn", fn);
// p1.then(fn) 这个时候才去执行 fn,拿到返回的 module.export,并且把这个结果再封装成一个 Promise 实例
const p2 = p1.then(fn);
console.log("p2", p2);
p2.then((module) => {
  console.log(module.default(6, 9));
});
1
2
3
4
5
6
7
8
9
10

这是运行后的结果

这样拆解之后,可以看到 import('./sum') 转化为了 __webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(__webpack_require__, 2)) 做了如下一些事情:

  1. __webpack_require__.e(/* import() */ 1)

    • 通过 Promise.all 返回一个 Promise 实例 p1
  2. p1.then(__webpack_require__.bind(__webpack_require__, 2))

    • 当 p1 的状态变为 fulfilled ,执行 .then 中的函数 __webpack_require__(2),这个函数返回对应的 module.exports,被 then 处理后为一个 状态为 fulfilled的 Promise 实例,实例结果值就为 sum 模块导出值

然后再一步一步看看每个环节具体做了哪些事情

# 准备加载 1.js

# __webpack_require__.e

输入:chunkId

输出:通过 Promise.all 返回一个 Promise 实例

作用:通过这个函数调用 __webpack_require__.f.j__webpack_require__.f.j 调用 __webpack_require__.l ,所以后面明确 __webpack_require__.f.j__webpack_require__.l 作用之后,就知道 __webpack_require__.e 作用是啥啦~

/******/ /* webpack/runtime/ensure chunk */
/******/ (() => {
  /******/ __webpack_require__.f = {}; // This file contains only the entry chunk. // The chunk loading function for additional chunks
  /******/ /******/ /******/ __webpack_require__.e = (chunkId) => {
    // __webpack_require__.f -> { j: function }
    /******/ return Promise.all(
      Object.keys(__webpack_require__.f).reduce((promises, key) => {
        // 执行 __webpack_require__.f.j
        /******/ __webpack_require__.f[key](chunkId, promises);
        /******/ return promises;
        /******/
      }, [])
    );
    /******/
  };
  /******/
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这个时候必须要往下执行

# __webpack_require__.f.j

输入:chunkId, promises

输出:undefined

作用:为加载 chunk 做一些准备

  1. installedChunks 存放 已经加载了 和 正在加载 的 chunks,标记它们的加载状态
  2. 通过 __webpack_require__.p + __webpack_require__.u(chunkId) 得到需要加载 chunk 的 url
  3. 进行错误处理 -> 未成功加载到某个 chunk
  4. 调用 __webpack_require__.l

注:这里的分析省略了一些逻辑判断

/******/ /* webpack/runtime/jsonp chunk loading */
/******/ (() => {
  /******/ // no baseURI
  /******/

  /******/ // object to store loaded and loading chunks
  /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
  /******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
  // installedChunks 存放 已经加载了 和 正在加载 的 chunks
  // 有几种取值
  // 0                          -> 已经加载了的 chunk
  // [resolve, reject, Promise] -> 正在加载的 chunk
  // undefined                  -> chunk 没有加载
  // null                       -> chunk preloaded/prefetched
  /******/ var installedChunks = {
    /******/ 0: 0,
    /******/
  };
  /******/

  /******/ __webpack_require__.f.j = (chunkId, promises) => {
    /******/ // JSONP chunk loading for javascript
    /******/ var installedChunkData = __webpack_require__.o(
      installedChunks,
      chunkId
    )
      ? installedChunks[chunkId]
      : undefined;
    /******/ if (installedChunkData !== 0) {
      // 0 means "already installed".
      /******/

      /******/ // a Promise means "currently loading".
      /******/ if (installedChunkData) {
        /******/ promises.push(installedChunkData[2]);
        /******/
      } else {
        /******/ if (true) {
          // all chunks have JS
          /******/ // setup Promise in chunk cache
          // installedChunkData 先赋值为 [resolve, reject]
          // 注意这里的 resolve,有用,后面在成功加载到 1.js 后就是执行这个 resolve(),改变了 promise 的状态
          /******/ var promise = new Promise(
            (resolve, reject) =>
              (installedChunkData = installedChunks[chunkId] = [
                resolve,
                reject,
              ])
          );
          // installedChunkData -> [resolve, reject, Promise]
          // 这个时候 promise 实例的状态为 pending
          /******/ promises.push((installedChunkData[2] = promise)); // start chunk loading
          /******/

          /******/ // 得到需要加载  chunk 的 url
          /******/ var url =
            __webpack_require__.p + __webpack_require__.u(chunkId); // create error before stack unwound to get useful stacktrace later
          /******/ // 进行错误处理 -> 未成功加载到某个 chunk
          /******/ var error = new Error();
          /******/ var loadingEnded = (event) => {
            /******/ if (__webpack_require__.o(installedChunks, chunkId)) {
              /******/ installedChunkData = installedChunks[chunkId];
              /******/ if (installedChunkData !== 0)
                installedChunks[chunkId] = undefined;
              /******/ if (installedChunkData) {
                /******/ var errorType =
                  event && (event.type === "load" ? "missing" : event.type);
                /******/ var realSrc =
                  event && event.target && event.target.src;
                /******/ error.message =
                  "Loading chunk " +
                  chunkId +
                  " failed.\n(" +
                  errorType +
                  ": " +
                  realSrc +
                  ")";
                /******/ error.name = "ChunkLoadError";
                /******/ error.type = errorType;
                /******/ error.request = realSrc;
                /******/ installedChunkData[1](error);
                /******/
              }
              /******/
            }
            /******/
          };
          // 调用 __webpack_require__.l
          /******/ __webpack_require__.l(
            url,
            loadingEnded,
            "chunk-" + chunkId,
            chunkId
          );
          /******/
        } else installedChunks[chunkId] = 0;
        /******/
      }
      /******/
    }
    /******/
  };
  /******/
  /******/
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

这个时候仍然需要继续往下分析

# __webpack_require__.l

输入:url, done, key, chunkId

输出:undefined

作用:通过增加 script 标签的方式加载 chunk

/******/ 	/* webpack/runtime/load script */
/******/ 	(() => {
/******/ 		var inProgress = {};
/******/ 		// data-webpack is not used as build has no uniqueName
/******/ 		// loadScript function to load a script via script tag
/******/ 		__webpack_require__.l = (url, done, key, chunkId) => {
/******/ 			if(inProgress[url]) { inProgress[url].push(done); return; }
/******/ 			var script, needAttach;
/******/ 			if(key !== undefined) {
/******/ 				var scripts = document.getElementsByTagName("script");
/******/ 				for(var i = 0; i < scripts.length; i++) {
/******/ 					var s = scripts[i];
/******/ 					if(s.getAttribute("src") == url) { script = s; break; }
/******/ 				}
/******/ 			}
/******/ 			if(!script) {
/******/ 				needAttach = true;
/******/ 				script = document.createElement('script');
/******/
/******/ 				script.charset = 'utf-8';
/******/ 				script.timeout = 120;
/******/ 				if (__webpack_require__.nc) {
/******/ 					script.setAttribute("nonce", __webpack_require__.nc);
/******/ 				}
/******/
/******/ 				script.src = url;
/******/ 			}
/******/ 			inProgress[url] = [done];
/******/ 			var onScriptComplete = (prev, event) => {
/******/ 				// avoid mem leaks in IE.
/******/ 				script.onerror = script.onload = null;
/******/ 				clearTimeout(timeout);
/******/ 				var doneFns = inProgress[url];
/******/ 				delete inProgress[url];
                // 注意这里,当通过 script 加载 chunk 完成之后,会删除这个 script 标签
/******/ 				script.parentNode && script.parentNode.removeChild(script);
/******/ 				doneFns && doneFns.forEach((fn) => (fn(event)));
/******/ 				if(prev) return prev(event);
/******/ 			};
/******/ 			var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
/******/ 			script.onerror = onScriptComplete.bind(null, script.onerror);
/******/ 			script.onload = onScriptComplete.bind(null, script.onload);
/******/ 			needAttach && document.head.appendChild(script);
/******/ 		};
/******/ 	})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

needAttach && document.head.appendChild(script); 这段代码执行前:

needAttach && document.head.appendChild(script); 这段代码执行后:

如图,__webpack_require__.l 函数执行完,head 中多了 <script charset="utf-8" src="http://localhost:3003/build/1.js"></script>,这个时候就开始加载 1.js 了

# 加载并执行 1.js

"use strict";
(self["webpackChunk"] = self["webpackChunk"] || []).push([
  [1],
  {
    /***/ 2: /***/ (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
        /* harmony export */
      });
      const sum = (a, b) => {
        return a + b;
      };

      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = sum;

      /***/
    },
  },
]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

self["webpackChunk"] || []).push 在最开始的时候的时候就定义了

/******/ 		var chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || [];
/******/ 		chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
/******/ 		chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
1
2
3

于是就走进 webpackJsonpCallback 这个函数中


# webpackJsonpCallback

输入:parentChunkLoadingFunction, data

输出:undefined

作用:

  1. 将加载到的 sum 模块放到 __webpack_modules__

    • __webpack_require__.m[moduleId] = moreModules[moduleId];
  2. 将 p1 Promise 实例状态变为 fulfilled

    • installedChunks[chunkId][0]();

注意:__webpack_require__.m = __webpack_modules__; 说明它们都指向同一个内存地址,所以 __webpack_require__.m 改变,__webpack_modules__也会跟着改变

/******/ // install a JSONP callback for chunk loading
/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
  /******/ var [chunkIds, moreModules, runtime] = data; // add "moreModules" to the modules object, // then flag all "chunkIds" as loaded and fire callback
  /******/ /******/ /******/ var moduleId,
    chunkId,
    i = 0;
  /******/ if (chunkIds.some((id) => installedChunks[id] !== 0)) {
    /******/ for (moduleId in moreModules) {
      /******/ if (__webpack_require__.o(moreModules, moduleId)) {
        /******/ __webpack_require__.m[moduleId] = moreModules[moduleId];
        /******/
      }
      /******/
    }
    /******/ if (runtime) var result = runtime(__webpack_require__);
    /******/
  }
  /******/ if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
  /******/ for (; i < chunkIds.length; i++) {
    /******/ chunkId = chunkIds[i];
    /******/ if (
      __webpack_require__.o(installedChunks, chunkId) &&
      installedChunks[chunkId]
    ) {
      /******/ installedChunks[chunkId][0]();
      /******/
    }
    /******/ installedChunks[chunkId] = 0;
    /******/
  }
  /******/
  /******/
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 加载 sum 模块

p1.then(__webpack_require__.bind(__webpack_require__, 2))

通过上述处理之后,__webpack_modules__已经有 sum 模块了

也就是

// __webpack_modules__
[
  ,
  /* 0 */ /* 1 */
  /***/ (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    __webpack_require__.r(__webpack_exports__);
    /* harmony export */ __webpack_require__.d(__webpack_exports__, {
      /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
      /* harmony export */
    });
    /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = "test";

    /***/
  },
  /* 2 */

  /***/ (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    __webpack_require__.r(__webpack_exports__);
    /* harmony export */ __webpack_require__.d(__webpack_exports__, {
      /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
      /* harmony export */
    });
    const sum = (a, b) => {
      return a + b;
    };

    /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = sum;

    /***/
  },
  /******/
];
/************************************************************************/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

这里就走加载静态 import 一样的逻辑了,可以转到 Webpack 运行时代码分析 - ESM 静态导入模块 (opens new window) 这篇文章看一下,这里不再分析。

# 小结

简单总结一下:__webpack_modules__没有一开始就存放动态加载的模块,而是在满足设定的条件后(比如本示例是点击 load sum 按钮),通过 JSONP 的方式加载动态模块,并且通过 webpackJsonpCallback(JSONP callback)将加载的模块放到 __webpack_modules__中,并将 __webpack_require__.e 返回的 Promise 实例状态变为 fulfilled,然后就可以通过 __webpack_require__ 加载模块了。

# 补充

文章并没有完全解答开篇提出的问题,有的需要读者自己思考并且实践一下,如果还有问题,可以在评论区留言,我会尽力回答~

上次更新: 2024年3月10日星期日上午10点19分